#include "RPN.h"
#include <cmath>
#include <cctype>
#include <iostream>
#include <sstream>
#include <string>
#include <stack>
using namespace std;

#define val_token 0
#define const_token 1
#define sep_token 2
#define keyword_token 3
#define unknown_token 4

#define sin_f 's'
#define cos_f 'c'
#define inv_f 'i'


// Паблики
//// Конструктор - калькулятор.
RPN::RPN(char * s) {
    Buff = new string(s);
    strBegIdx = 0;
    // Реализация команды quit для выхода из главного цикла.
    if (!Buff->compare("quit")) {
        cout << "Good bye !!!" << endl;
        throw 0;
    }
    int prevToken = 1; // 0 - константа, 1 - операция. Необходимо для распознавания унарного и бинарного минусов&
    int token; // Токен, к которому принадлежит следующая лексема.
    string* res; // Для лексемы.
    while (1) {
        res = getNextPart(token); // Считываем лексему.
        //cout << token << " " << (*res) << endl; // Для дебага - просмотр лексем.
        // Обработка конца строки.
        if (!res) {
            while (!oper.empty()) {
                char top = oper.top();
                oper.pop();
                calculate(top);
            }
            break;
        }
        // Обработка недопустимых лексем.
        if (token == unknown_token) {
            cout << "Unknown token: " << "\"" + (*res) + "\"" << endl;
            throw "Bad sentence!";
        }
        // Обработка констант.
        if (token == val_token || token == const_token) {
            double x;
            if (!token)
                x = fromValToken(res); // Вещественные константы.
            else
                x = fromConstToken(res); // Константы e и pi
            val.push(x);
            prevToken = 0;
            continue;
        }
        // Обработка операций
        if (token == sep_token || token == keyword_token) {
            char op = strToOper(*res); // Кодируем операцию символом.
            if (op == '(') {
                oper.push('(');
                prevToken = 1;
                continue;
            } else if (op == ')') { // По достижении ')' выгружаем поочередно 
                char top; // из стека и выполняем.
                while (1) {
                    if (oper.empty())
                        throw "Invalid Scobe Sequence!";
                    top = oper.top();
                    oper.pop();
                    if (top == '(')
                        break;
                    else
                        calculate(top);
                }
                prevToken = 0;
                continue;
            } else if (op == '-' && prevToken) { // Распознавание унарного минуса.
                oper.push(inv_f);
                continue;
            }
            // Основа - работа со стеком, учитывая приоритеты операций.
            // Рассчет производится при извлечении символа-операции из стека.
            if (oper.empty()) {
                oper.push(op);
                prevToken = 1;
            } else {
                char top = oper.top();
                int prior = operPriority(op) - operPriority(top);
                if (prior > 0)
                    oper.push(op);
                else if (!prior) {
                    calculate(top);
                    oper.pop();
                    oper.push(op);
                } else if (prior < 0) {
                    do {
                        calculate(top);
                        oper.pop();
                        if (oper.empty())
                            break;
                        top = oper.top();
                        prior = operPriority(op) - operPriority(top);
                    } while (prior < 0);
                    oper.push(op);
                }
                prevToken = 1;
            }
        }
    }
}

RPN::~RPN(){
    if(Buff)
        delete Buff;
}

//// Извлечение результата из стека. 
double RPN::result() {
    double res = val.top();
//    if (res < 1.0e-15 && res > -1.0e-15) // Отслеживание нуля.
//        return 0.0;
//    else
        return res;
}

//// Печать результата.
void RPN::print() {
    cout << "Result: ";
    if (val.empty())
        cout << "No Answer." << endl;
    else {
        double x = result();
        cout << x << endl;
    }
}

// Разбор выражения.
//// Возвращает следующую лексему из строки 
//// и меняет токен-параметр для распознавания.
string * RPN::getNextPart(int& token) {
    if (strBegIdx == Buff->size())
        return NULL;
    int cur[5];
    int prev[5];
    int states[5];
    token = -1;
    //Инициализация массивов состояний.
    for (int i = 0; i < 5; i++) {
        prev[i] = cur[i] = 1;
        states[i] = 0;
    }
    // Убираем пробельные символы
    while ((*Buff)[strBegIdx] == ' ' || (*Buff)[strBegIdx] == '\t')
        strBegIdx++;
    // Пробег до конца строки + холостой ход
    // для принудительной поломки автоматов.
    for (int i = strBegIdx; i <= Buff->size(); i++) {
        // Принудительная поломка автоматов.
        if (i == Buff->size()) {
            for (int j = 0; j < 5; j++) {
                prev[j] = cur[j];
                cur[j] = 0;
            }
        } else {
            // Пуск символа в автоматы -> изменениие состояний.
            if (cur[0])
                cur[0] = valMac((*Buff)[i], states[0]);
            else
                prev[0] = 0;
            if (cur[1])
                cur[1] = constMac((*Buff)[i], states[1]);
            else
                prev[1] = 0;
            if (cur[2]) {
                cur[2] = sepMac((*Buff)[i], states[2]);
                if (cur[2])
                    cur[4] = 0;
            } else
                prev[2] = 0;
            if (cur[3])
                cur[3] = keywordMac((*Buff)[i], states[3]);
            else
                prev[3] = 0;
            if (!(cur[0] || cur[1] || cur[2] || cur[3]))
                states[4] = 1;
            if (cur[4] && states[4])
                cur[4] = unknownMac((*Buff)[i]);
        }
        if (!cur[0] && !cur[1] && !cur[2] && !cur[3] && !cur[4]) {
            string *res = new string;
            res->append((*Buff), strBegIdx, i - strBegIdx);
            if (prev[0] && valFin(states[0])) token = val_token;
            if (prev[1] && constFin(states[1])) token = const_token;
            if (prev[2] && sepFin(states[2])) token = sep_token;
            if (prev[3] && keywordFin(states[3])) token = keyword_token;
            if (prev[4] && token == -1) token = unknown_token;
            if ((*Buff)[i] == 32)
                i++;
            strBegIdx = i;
            return res;
        }
    }
}

// Константы
//// Преобразование лексемы константы в double.
double RPN::fromValToken(string* str) {
    double res = 0.0;
    istringstream sst(*str);
    sst >> res;
    return res;
}
//// Раскрытие значений e и pi
double RPN::fromConstToken(string* str) {
    double res = 0.0;
    if (!str->compare("e"))
        res = exp(1.0);
    else if (!str->compare("pi"))
        res = acos((double) (-1));
    return res;
}

// Обратная польская
//// Кодируем операцию символом. 
char RPN::strToOper(string& s) {
    if (s.size() == 1) {
        if (s[0] == '[' || s[0] == '{') // Разные скобки для удобства ввода.
            return '(';
        else if (s[0] == ']' || s[0] == '}')
            return ')';
        else
            return s[0]; // Для односимвольных операций.
    } else {
        if (!s.compare("sin")) // Для sin и cos.
            return sin_f;
        else
            return cos_f;
    }
}
//// Приоритет операции.
int RPN::operPriority(char& c) {
    if (c == '(')
        return 0;
    else if (c == '+' || c == '-')
        return 1;
    else if (c == '*' || c == '/')
        return 2;
    else if (c == '^' || c == sin_f || c == cos_f)
        return 3;
}
//// Исполнение операции по символу-коду.
void RPN::calculate(char& c) {
    if (c == '(')
        throw "Invalid Scobe Sequence";
    if (c > '0' && c != '^') { // Унарные.
        double(*fUn)(double);
        if (val.empty())
            throw "Lack of Values!";
        double a = val.top();
        val.pop();
        if (c == sin_f)
            fUn = &sinRPN;
        else if (c == cos_f)
            fUn = &cosRPN;
        else if (c == inv_f)
            fUn = &invSign;
        val.push(fUn(a));
    } else if (c < '0' || c == '^') { // Бинарные.
        double(*fBin)(double, double);
        if (val.empty())
            throw "Lack of Values!";
        double a = val.top();
        val.pop();
        if (val.empty())
            throw "Lack of Values!";
        double b = val.top();
        val.pop();
        if (c == '+')
            fBin = &summ;
        else if (c == '-')
            fBin = &substr;
        else if (c == '*')
            fBin = &mult;
        else if (c == '/')
            fBin = &divis;
        else if (c == '^')
            fBin = &inDegree;
        val.push(fBin(b, a)); 
    }
    // Результат на вершине стека.
}

// Функции счета

double summ(double a, double b) {
    return a + b;
}

double substr(double a, double b) {
    return a - b;
}

double mult(double a, double b) {
    return a * b;
}

double divis(double a, double b) {
    if (b < 1.0e-10)
        throw "Division by zero!";
    return a / b;
}

double inDegree(double a, double b) {
    if ((a < 1.0e-10 && b < 0.0) || (a < 0.0 && (round(b) - b)))
        throw "Bad Involution!";
    return pow(a, b);
}

double invSign(double a) {
    return a * (-1);
}

double sinRPN(double a){
    double x = sin(a);
    if(x < 1.0e-15 && x > -1.0e-15)
        return 0.0;
    else
        return x;
}

double cosRPN(double a){
    double x = cos(a);
    if(x < 1.0e-15 && x > -1.0e-15)
        return 0.0;
    else
        return x;
}

// Автоматы
// Реализация работы 5-ти конечных детерминированных автоматов.
int RPN::valMac(char c, int &state) {
    int alive = 1;
    switch (state) {
        case 0:
            if (isdigit(c)) {
                state = 1;
                break;
            }
            if (c == '.') {
                state = 10;
                break;
            }
            alive = 0;
            break;
        case 1:
            if (c == '.') {
                state = 2;
                break;
            }
            if (isdigit(c)) {
                state = 7;
                break;
            }
            alive = 0;
            break;
        case 2:
            if (isdigit(c)) {
                state = 3;
                break;
            }
            alive = 0;
            break;
        case 3:
            if (isdigit(c)) break;
            if (c == 'e') {
                state = 4;
                break;
            }
            alive = 0;
            break;
        case 4:
            if (isdigit(c)) {
                state = 6;
                break;
            }
            if (c == '+' || c == '-') {
                state = 5;
                break;
            }
            alive = 0;
            break;
        case 5:
            if (isdigit(c)) {
                state = 6;
                break;
            }
            alive = 0;
            break;
        case 6:
            if (isdigit(c)) break;
            alive = 0;
            break;
        case 7:
            if (isdigit(c)) break;
            if (c == '.') {
                state = 8;
                break;
            }
            alive = 0;
            break;
        case 8:
            if (isdigit(c)) {
                state = 9;
                break;
            }
            alive = 0;
            break;
        case 9:
            if (isdigit(c)) break;
            alive = 0;
            break;
        case 10:
            if (isdigit(c)) {
                state = 11;
                break;
            }
            alive = 0;
            break;
        case 11:
            if (isdigit(c)) break;
            alive = 0;
            break;
    }
    return alive;
}

bool RPN::valFin(int &state) {
    if (state == 0 || state == 4 || state == 10)
        return false;
    return true;
}

int RPN::constMac(int c, int &state) {
    int alive = 1;
    switch (state) {
        case 0:
            if (c == 'e') {
                state = 1;
                break;
            }
            if (c == 'p') {
                state = 2;
                break;
            }
            alive = 0;
            break;
        case 1:
            alive = 0;
            break;
        case 2:
            if (c == 'i') {
                state = 3;
                break;
            }
            alive = 0;
            break;
        case 3:
            alive = 0;
            break;
    }
    return alive;
}

bool RPN::constFin(int &state) {
    if (state == 1 || state == 3)
        return true;
    return false;
}

int RPN::sepMac(char c, int &state) {
    int alive = 1;
    if (state == 0) {
        if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' ||
                c == '+' || c == '-' || c == '*' || c == '/' || c == '^')
            state = 1;
        else
            alive = 0;
    } else
        alive = 0;
    return alive;
}

bool RPN::sepFin(int &state) {
    if (state == 1)
        return true;
    return false;
}

int RPN::keywordMac(char c, int &state) {
    int alive = 1;
    switch (state) {
        case 0:
            if (c == 'c') {
                state = 1;
                break;
            }            
            if (c == 's') {
                state = 4;
                break;
            }
            alive = 0;
            break;
        case 1:
            if (c == 'o') {
                state = 2;
                break;
            }
            alive = 0;
            break;
        case 2:
            if (c == 's') {
                state = 3;
                break;
            }
            alive = 0;
            break;
        case 3:
            alive = 0;
            break;
        case 4:
            if (c == 'i') {
                state = 5;
                break;
            }
            alive = 0;
            break;
        case 5:
            if (c == 'n') {
                state = 6;
                break;
            }
            alive = 0;
            break;
        case 6:
            alive = 0;
            break;

    }
    return alive;
}

bool RPN::keywordFin(int &state) {
    if (state == 3 || state == 6)
        return true;
    return false;
}

int RPN::unknownMac(char c) {
    int alive = 1;
    if (c == ' ' || c == '\t' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '.' ||
            c == '+' || c == '-' || c == '^' || c == '*' || c == '/' || isdigit(c))
        alive = 0;
    return alive;
}